// Copyright (c) 2009, Michael Patraw
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * The name of Michael Patraw may not be used to endorse or promote 
//       products derived from this software without specific prior written
//       permission.
//
// THIS SOFTWARE IS PROVIDED BY Michael Patraw ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL Michael Patraw BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <ctime>
#include <string>

#include "forest.h"
#include "MersenneTwister.h"
#include "tui.h"
#include "util.h"

extern MTRand mt;

//==============================================================================
//==============================================================================

const ForestItem ForestItem::Stick("a stick", ID_HAND, TYPE_BLUNT | TYPE_POLE | TYPE_BURNS, 0, 0, 1);
const ForestItem ForestItem::Mud("some mud", ID_BODY, TYPE_CLAY, -2);
const ForestItem ForestItem::Dirt("some dirt", ID_NONE, TYPE_NONE);
const ForestItem ForestItem::Rock("a rock", ID_NONE, TYPE_STONE);
const ForestItem ForestItem::Grass("some grass", ID_BODY, TYPE_PLANT | TYPE_BURNS, 1);
const ForestItem ForestItem::DirtyWater("some dirty water", ID_NONE, TYPE_LIQUID);
const ForestItem ForestItem::Water("some water", ID_NONE, TYPE_LIQUID);
const ForestItem ForestItem::DeerSkin("a deer skin", ID_BODY, TYPE_COVER, 3, 0, 0, 2);
const ForestItem ForestItem::Meat("some meat", ID_NONE, TYPE_NONE, 0, 0, 0, 0);
const ForestItem ForestItem::Bone("a bone", ID_HAND, TYPE_BLUNT | TYPE_POLE, 0, 0, 2, 0);

const ForestItem ForestItem::Spear("a spear", ID_HAND, TYPE_SHARP | TYPE_POLE, 0, 0, 4, 0);
const ForestItem ForestItem::LeatherCoat("a leather coat", ID_BODY, TYPE_COVER, 4, 0, 0, 2);
const ForestItem ForestItem::GrassHelm("a grass helm", ID_HEAD, TYPE_BURNS, 1, 0, 0, 0);
const ForestItem ForestItem::GrassLegs("some grass leggings", ID_LEGS, TYPE_BURNS, 2, 0, 0, 1);
const ForestItem ForestItem::GrassBoots("a pair of grass boots", ID_FEET, TYPE_BURNS, 2, 0, 0, 0);
const ForestItem ForestItem::Knife("a knife", ID_HAND, TYPE_SHARP, 0, 0, 2, 0);
const ForestItem ForestItem::Tea("some tea", ID_NONE, TYPE_NONE, 0, 0, 0, 0);

ForestItem::ForestItem(const char * name, int id, type_t type, int warmth, int health, int attack, int defense) :
Item(name, id, type, warmth, health, attack, defense)
{

}

ForestItem::~ForestItem()
{

}

//==============================================================================
//==============================================================================

enum
{
    // Forest tiles.
    TILE_FOREST_BARREN = 0,
    TILE_FOREST_GRASS,
    TILE_FOREST_THICKGRASS,
    TILE_FOREST_DEADGRASS,
    TILE_FOREST_DIRT,
    TILE_FOREST_MUD,
    TILE_FOREST_ROCKS,
    TILE_FOREST_BOULDER,
    TILE_FOREST_BUSH,
    TILE_FOREST_TREE,
    TILE_FOREST_PINE_TREE,
    TILE_FOREST_RUNNING_WATER,
    TILE_FOREST_STILL_WATER,
    TILE_FOREST_STAGNANT_WATER,
    
    TILE_FOREST_MAX = 32,
};

TileTemplate forestset[TILE_FOREST_MAX] = {
    // Forest tiles.
    TileTemplate("bare earth", Glyph('.', Orange), true),
    TileTemplate("grass", Glyph('.', DarkGreen), true, &ForestItem::Grass),
    TileTemplate("thick grass", Glyph(',', DarkGreen), true),
    TileTemplate("dead grass", Glyph(',', DarkGoldenRod), true),
    TileTemplate("dirt", Glyph('.', DarkGoldenRod), true, &ForestItem::Dirt),
    TileTemplate("mud", Glyph(',', DarkGoldenRod), true, &ForestItem::Mud),
    TileTemplate("rocks", Glyph(';', Grey), true, &ForestItem::Rock),
    TileTemplate("boulders", Glyph(char(2), Grey), false), // TEMP
    TileTemplate("bushes", Glyph(char(3), YellowGreen), true, &ForestItem::Stick), // TEMP
    TileTemplate("trees", Glyph(char(0), Green), false), // TEMP
    TileTemplate("pine trees", Glyph(char(1), ForestGreen), false), // TEMP
    TileTemplate("running water", Glyph('~', Blue), true, &ForestItem::Water),
    TileTemplate("still water", Glyph(char(4), Blue), true, &ForestItem::DirtyWater), // TEMP
    TileTemplate("stagnant water", Glyph('=', Brown), true, &ForestItem::DirtyWater),
};

const ForestTile ForestTile::BARREN(TILE_FOREST_BARREN);
const ForestTile ForestTile::GRASS(TILE_FOREST_GRASS);
const ForestTile ForestTile::THICKGRASS(TILE_FOREST_THICKGRASS);
const ForestTile ForestTile::DEADGRASS(TILE_FOREST_DEADGRASS);
const ForestTile ForestTile::DIRT(TILE_FOREST_DIRT);
const ForestTile ForestTile::MUD(TILE_FOREST_MUD);
const ForestTile ForestTile::ROCKS(TILE_FOREST_ROCKS);
const ForestTile ForestTile::BOULDER(TILE_FOREST_BOULDER);
const ForestTile ForestTile::BUSH(TILE_FOREST_BUSH);
const ForestTile ForestTile::TREE(TILE_FOREST_TREE);
const ForestTile ForestTile::PINE_TREE(TILE_FOREST_PINE_TREE);
const ForestTile ForestTile::RUNNING_WATER(TILE_FOREST_RUNNING_WATER);
const ForestTile ForestTile::STILL_WATER(TILE_FOREST_STILL_WATER);
const ForestTile ForestTile::STAGNANT_WATER(TILE_FOREST_STAGNANT_WATER);

//==============================================================================
//==============================================================================

ForestTile::ForestTile() :
Tile(GRASS)
{
}

ForestTile::ForestTile(short index) :
Tile(index)
{
}

const char * ForestTile::getName() const
{
    return forestset[index_].name;
}

const Glyph & ForestTile::getGlyph() const
{
    return forestset[index_].glyph;
}
    
bool ForestTile::isPassable() const
{
    return forestset[index_].passable;
}

const Item * ForestTile::getGather() const
{
    return forestset[index_].gather;
}

//==============================================================================
//==============================================================================

struct Recipe {
    type_t type1, type2;
    const ForestItem & created;
};

const int NUM_RECIPES = 7;
Recipe forestrecipes[NUM_RECIPES] = {
    {TYPE_POLE, TYPE_SHARP, ForestItem::Spear},
    {TYPE_COVER, TYPE_PLANT, ForestItem::LeatherCoat},
    {TYPE_PLANT, TYPE_CLAY, ForestItem::GrassHelm},
    {TYPE_PLANT, TYPE_POLE, ForestItem::GrassLegs},
    {TYPE_PLANT, TYPE_PLANT, ForestItem::GrassBoots},
    {TYPE_STONE, TYPE_POLE, ForestItem::Knife},
    {TYPE_LIQUID, TYPE_PLANT, ForestItem::Tea},
};

ForestWorld::ForestWorld(Map * map, const char * name) :
World(map, name)
{
}

ForestWorld::~ForestWorld()
{
}

void ForestWorld::update()
{
    Coord pc = player_.getCoord();
    // Drowning if on a water tile. (20% chance)
    if (*(map_->getTile(pc.getX(), pc.getY())) == ForestTile::RUNNING_WATER ||
        *(map_->getTile(pc.getX(), pc.getY())) == ForestTile::STILL_WATER) {
        int ch = mt.rand(100);
        if (ch <= 20) {
            player_.kill("drowning");
        }
    }
    // High winds knock over nearby trees.
    bool neartree = false;
    
    if (*(map_->getTile(pc.getX(), pc.getY())) == ForestTile::TREE ||
        *(map_->getTile(pc.getX(), pc.getY())) == ForestTile::PINE_TREE)
        neartree = true;
    
    if (pc.getX() + 1 <= map_->getWidth())
    if (*(map_->getTile(pc.getX(), pc.getY())) == ForestTile::TREE ||
        *(map_->getTile(pc.getX(), pc.getY())) == ForestTile::PINE_TREE)
        neartree = true;
    
    if (pc.getX() - 1 >= 0)
    if (*(map_->getTile(pc.getX(), pc.getY())) == ForestTile::TREE ||
        *(map_->getTile(pc.getX(), pc.getY())) == ForestTile::PINE_TREE)
        neartree = true;
    
    if (pc.getY() + 1 < map_->getHeight())
    if (*(map_->getTile(pc.getX(), pc.getY() + 1)) == ForestTile::TREE ||
        *(map_->getTile(pc.getX(), pc.getY() + 1)) == ForestTile::PINE_TREE)
        neartree = true;
    
    if (pc.getY() - 1 >= 0)
    if (*(map_->getTile(pc.getX(), pc.getY() - 1)) == ForestTile::TREE ||
        *(map_->getTile(pc.getX(), pc.getY() - 1)) == ForestTile::PINE_TREE)
        neartree = true;
    
    if (neartree && weather_.getWind() >= 30) {
        int ch = mt.rand(100);
        if (ch <= 20) {
            player_.kill("a tree falling down");
        }
    }
    World::update();
}

void ForestWorld::gatherCommand()
{
    std::string str = "You gather ";
    Coord pc = player_.getCoord();
    
    Tile * tile = map_->tile(pc.getX(), pc.getY());
    
    if (!player_.getInv()->isFull() && tile->getGather() != NULL) {
        player_.getInv()->addItem(new Item(*(tile->getGather())));
        str += tile->getGather()->getName();
        MessWindow()->addstr(0, 1, str.c_str(), White);
        // Can't get rid of water. (easily)
        if (*tile != ForestTile::RUNNING_WATER &&
            *tile != ForestTile::STILL_WATER &&
            *tile != ForestTile::STAGNANT_WATER)
            *tile = ForestTile::BARREN;
    }
}

void ForestWorld::useCommand() {
    ItemList * list = player_.getInv();
    int pos, size = list->numItems();

    pos = selectItem(list, "= Viewing Inventory, press ENTER to equip =");
    
    if (pos >= 0 && pos < size) {
        // Try using it.
        if (*(list->getItem(pos)) == ForestItem::Mud) {
            player_.modHydration(10); // Only restores 10 hydration.
            list->delItem(pos);
        }
        else if (*(list->getItem(pos)) == ForestItem::Grass) {
            player_.modSatiation(10); // Only restores 10 statiation.
            list->delItem(pos);
        }
        else if (*(list->getItem(pos)) == ForestItem::Water) {
            player_.modHydration(50);
            list->delItem(pos);
        }
        else if (*(list->getItem(pos)) == ForestItem::DirtyWater) {
            player_.modHydration(50);
            player_.health()->current() -= 2;
            list->delItem(pos);
        }
        else if (*(list->getItem(pos)) == ForestItem::Meat) {
            player_.modSatiation(50);
            list->delItem(pos);
        }
        else if (*(list->getItem(pos)) == ForestItem::Tea) {
            player_.modHydration(50);
            player_.modSatiation(25);
            list->delItem(pos);
            MessWindow()->addstr(0, 0, "Good stuff!", White);
        }
    }
}

void ForestWorld::createItem(ItemList * list)
{
    // Works on first two items in inventory (top).
    if (list->numItems() < 2)
        return;
    
    for (int i = 0; i < NUM_RECIPES; ++i) {
        // Compare, then in reverse order.
        if ((list->getItem(0)->getType() & forestrecipes[i].type1 && 
            list->getItem(1)->getType() & forestrecipes[i].type2) ||
            (list->getItem(1)->getType() & forestrecipes[i].type1 && 
            list->getItem(0)->getType() & forestrecipes[i].type2)) {
            list->delItem(0);
            list->delItem(0);
            list->addItem(new ForestItem(forestrecipes[i].created));
            MessWindow()->addstr(0, 0, "Created an item!", White);
            break;
        }
    }
}

//==============================================================================
//==============================================================================

void _SetThreeTiles(Map * map, int x1, int y1, int x2, int y2, int x3, int y3, const ForestTile & tile);

// Very very poor algorithm, not cheap either.
Map * GenerateForest(int w, int h)
{
    Map * map = new Map(w, h);
    
    // Duplicates to less then number of blockers.
    const int numg = 15;
    ForestTile gtiles[numg] = {
        ForestTile::TREE,
        ForestTile::PINE_TREE,
        ForestTile::BOULDER,
        ForestTile::GRASS,
        ForestTile::GRASS,
        ForestTile::GRASS,
        ForestTile::THICKGRASS,
        ForestTile::THICKGRASS,
        ForestTile::DEADGRASS,
        ForestTile::DEADGRASS,
        ForestTile::DIRT,
        ForestTile::DIRT,
        ForestTile::MUD,
        ForestTile::ROCKS,
        ForestTile::BUSH,
    };
    
    // Start populating the map with random grounds.
    for (int x = 0; x < w; ++x) {
        for (int y = 0; y < h; ++y) {
            int index = mt.randInt(numg - 1);
            
            map->setTile(x, y, new ForestTile(gtiles[index]));
        }
    }
    
    // Create three lakes (roughly 15 by 15).
    for (int c = 0; c < 3; ++c) {
        Coord lakeloc(mt.randInt(w - 1), mt.randInt(h - 1));
        map->setTile(lakeloc.getX(), lakeloc.getY(), new ForestTile(ForestTile::STILL_WATER));
        int dx = 0, dy = 0, dir;
        for (int i = 0; i < 100; ++i) {
            if (i % 2 == 0)
                dir = mt.randInt(7);
            
            GetDirection((Dir)dir, &dx, &dy);
            
            lakeloc.modX(dx);
            lakeloc.modY(dy);
            
            switch (dir) {
                case DIR_UP: case DIR_DOWN:
                    _SetThreeTiles(map, lakeloc.getX(), lakeloc.getY(),
                                   lakeloc.getX() + 1, lakeloc.getY(),
                                   lakeloc.getX() - 1, lakeloc.getY(),
                                   ForestTile::STILL_WATER);
                    break;
                case DIR_LEFT: case DIR_RIGHT:
                    _SetThreeTiles(map, lakeloc.getX(), lakeloc.getY(),
                                   lakeloc.getX(), lakeloc.getY() + 1,
                                   lakeloc.getX(), lakeloc.getY() - 1,
                                   ForestTile::STILL_WATER);
                    break;
                case DIR_UPLEFT: case DIR_DOWNRIGHT:
                case DIR_UPRIGHT: case DIR_DOWNLEFT:
                    _SetThreeTiles(map, lakeloc.getX(), lakeloc.getY(),
                                   lakeloc.getX(), lakeloc.getY() + (dy * -1),
                                   lakeloc.getX() + (dx * -1), lakeloc.getY(),
                                   ForestTile::STILL_WATER);
                    break;
            }
        }

        // River. Start from the lake, then go towards the edge of the map.
        dir = mt.randInt(7); // General river direction.
        GetDirection((Dir)dir, &dx, &dy);
        for (int i = 0; i < 100; ++i) {
            int path = mt.randInt(2);
            switch (dir) {
                case DIR_UP: case DIR_DOWN:
                    if (path == 0) {
                        lakeloc.modX(dx);
                        lakeloc.modY(dy);
                    }
                    else if (path == 1) {
                        lakeloc.modX(dx + 1);
                        lakeloc.modY(dy);
                    }
                    else if (path == 2) {
                        lakeloc.modX(dx - 1);
                        lakeloc.modY(dy);
                    }
                    break;
                case DIR_LEFT: case DIR_RIGHT:
                    if (path == 0) {
                        lakeloc.modX(dx);
                        lakeloc.modY(dy);
                    }
                    else if (path == 1) {
                        lakeloc.modX(dx);
                        lakeloc.modY(dy + 1);
                    }
                    else if (path == 2) {
                        lakeloc.modX(dx);
                        lakeloc.modY(dy - 1);
                    }
                    break;
                case DIR_UPLEFT: case DIR_DOWNRIGHT:
                case DIR_UPRIGHT: case DIR_DOWNLEFT:
                    if (path == 0) {
                        lakeloc.modX(dx);
                        lakeloc.modY(dy);
                    }
                    else if (path == 1) {
                        lakeloc.modX((dx * -1));
                        lakeloc.modY(dy);
                    }
                    else if (path == 2) {
                        lakeloc.modX(dx);
                        lakeloc.modY((dy * -1));
                    }
                    break;
            }
            if (lakeloc.getX() >= 0 && lakeloc.getX() < map->getWidth() &&
                lakeloc.getY() >= 0 && lakeloc.getY() < map->getHeight())
            if (*(map->getTile(lakeloc.getX(), lakeloc.getY())) != ForestTile::STILL_WATER)
                map->setTile(lakeloc.getX(), lakeloc.getY(), new ForestTile(ForestTile::RUNNING_WATER));
        }    
    }
    
    // Litter the map with stagnant water.
    int x, y;
    for (int i = 0; i < 50; ++i) {
        x = mt.randInt(w - 1);
        y = mt.randInt(h - 1);
        map->setTile(x, y, new ForestTile(ForestTile::STAGNANT_WATER));
    }
    
    // Add some dead animals.
    for (int i = 0; i < 50; ++i) {
        x = mt.randInt(w - 1);
        y = mt.randInt(h - 1);
        if (map->tile(x, y)->isPassable()) {
            map->tile(x, y)->getItems()->addItem(new ForestItem(ForestItem::DeerSkin));
            map->tile(x, y)->getItems()->addItem(new ForestItem(ForestItem::Meat));
            map->tile(x, y)->getItems()->addItem(new ForestItem(ForestItem::Meat));
            map->tile(x, y)->getItems()->addItem(new ForestItem(ForestItem::Bone));
        }
        else
            --i;
    }
     
    return map;
}

void _SetThreeTiles(Map * map, int x1, int y1, int x2, int y2, int x3, int y3, const ForestTile & tile)
{
    if (x1 >= 0 && x1 < map->getWidth() &&
        y1 >= 0 && y1 < map->getHeight())
        map->setTile(x1, y1, new ForestTile(tile));
        
    if (x2 >= 0 && x2 < map->getWidth() &&
        y2 >= 0 && y2 < map->getHeight())
        map->setTile(x2, y2, new ForestTile(tile));
        
    if (x3 >= 0 && x3 < map->getWidth() &&
        y3 >= 0 && y3 < map->getHeight())
        map->setTile(x3, y3, new ForestTile(tile));
}
